/***** BEGIN LICENSE BLOCK *****
 * Version: CPL 1.0/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Common Public
 * License Version 1.0 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.eclipse.org/legal/cpl-v10.html
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * Copyright (C) 2001 Alan Moore <alan_moore@gmx.net>
 * Copyright (C) 2001-2002 Benoit Cerrina <b.cerrina@wanadoo.fr>
 * Copyright (C) 2001-2004 Jan Arne Petersen <jpetersen@uni-bonn.de>
 * Copyright (C) 2002-2004 Anders Bengtsson <ndrsbngtssn@yahoo.se>
 * Copyright (C) 2004-2005 Thomas E Enebo <enebo@acm.org>
 * Copyright (C) 2004 Stefan Matthias Aust <sma@3plus4.de>
 * Copyright (C) 2005 David Corbin <dcorbin@users.sourceforge.net>
 * Copyright (C) 2006 Nick Sieger <nicksieger@gmail.com>
 * Copyright (C) 2006 Miguel Covarrubias <mlcovarrubias@gmail.com>
 * 
 * Alternatively, the contents of this file may be used under the terms of
 * either of the GNU General Public License Version 2 or later (the "GPL"),
 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the CPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the CPL, the GPL or the LGPL.
 ***** END LICENSE BLOCK *****/
package asruby.lang {

import asruby.Ruby;
import asruby.runtime.Block;
import asruby.runtime.CallbackFactory;
import asruby.runtime.ClassIndex;
import asruby.runtime.Frame;
import asruby.runtime.IRubyObject;
import asruby.runtime.ThreadContext;
import asruby.util.KCode;

import flash.utils.Dictionary;

/**
 *
 */
public class RubyRegexp extends RubyObject { //implements ReOptions, WarnCallback {
    private  var _kcode:KCode;
    private  var pattern:Regex;
    private  var str:ByteList;

    private static var PIPE: Array =  ['|'];
    private static var DASH: Array =  ['-'];
    private static var R_PAREN: Array =  [')'];
    private static var COLON: Array =  [':'];
    private static var M_CHAR: Array =  ['m'];
    private static var I_CHAR: Array =  ['i'];
    private static var X_CHAR: Array =  ['x'];

    private static var REGEXP_LITERAL_F: int =      1 << 11;
    private static var REGEXP_KCODE_DEFAULT: int =  1 << 12;

    public  function setLiteral():void{
        flags |= REGEXP_LITERAL_F;
    }
    
    public  function clearLiteral():void{
        flags &= ~REGEXP_LITERAL_F;
    }
    
    public  function isLiteral():Boolean{
        return (flags & REGEXP_LITERAL_F) != 0;
    }

    public  function setKCodeDefault():void{
        flags |= REGEXP_KCODE_DEFAULT;
    }
    
    public  function clearKCodeDefault():void{
        flags &= ~REGEXP_KCODE_DEFAULT;
    }
    
    public  function isKCodeDefault():Boolean{
        return (flags & REGEXP_KCODE_DEFAULT) != 0;
    }

    public  function getKCode():KCode{
        return kcode;
    }

    private static function getPatternCache():Dictionary{
        var cache: HashMap =  patternCache.get();
        if (cache == null) {
            cache = new HashMap(5);
            patternCache = new SoftReference<HashMap>(cache);
        }
        return cache;
    }

    static var patternCache:Dictionary = new Dictionary;    

    public static function createRegexpClass(runtime:Ruby):RubyClass{
        var regexpClass: RubyClass =  runtime.defineClass("Regexp", runtime.getObject(), REGEXP_ALLOCATOR);
        runtime.setRegexp(regexpClass);
        regexpClass.index = ClassIndex.REGEXP;
        regexpClass.kindOf = 
                function isKindOf(obj:IRubyObject, type:RubyModule):Boolean{
                    return obj instanceof RubyRegexp;
                };
        
        var callbackFactory: CallbackFactory =  runtime.callbackFactory(RubyRegexp);
        
        regexpClass.defineConstant("IGNORECASE", runtime.newFixnum(RE_OPTION_IGNORECASE));
        regexpClass.defineConstant("EXTENDED", runtime.newFixnum(RE_OPTION_EXTENDED));
        regexpClass.defineConstant("MULTILINE", runtime.newFixnum(RE_OPTION_MULTILINE));
        
        regexpClass.defineAnnotatedMethods(RubyRegexp);
        regexpClass.dispatcher = callbackFactory.createDispatcher(regexpClass);

        return regexpClass;
    }
    
    private static var REGEXP_ALLOCATOR: Function = 
        function allocate(runtime:Ruby, klass:RubyClass):IRubyObject{
            var instance: RubyRegexp =  new RubyRegexp(runtime, klass);
            return instance;
        };
        
    /** used by allocator
     * 
     */
    function RubyRegexp(runtime:Ruby, klass:RubyClass){
        super(runtime, klass);
    }

    /** default constructor
     * 
     */
/*     private  function RubyRegexp(runtime:Ruby){
        super(runtime, runtime.getRegexp());
    } */

    // used only by the compiler/interpreter (will set the literal flag)
    public static function newRegexp(runtime:Ruby, pattern:String, options:int):RubyRegexp{
        return newRegexp(runtime, ByteList.create(pattern), options);
    }

    // used only by the compiler/interpreter (will set the literal flag)
    public static function newRegexp2(runtime:Ruby, pattern:String, options:int):RubyRegexp{
        var regexp: RubyRegexp =  newRegexp(runtime, pattern, options, false);
        regexp.setLiteral();
        return regexp;
    }
    
    public static function newRegexp3(runtime:Ruby, pattern:String, options:int, quote:Boolean):RubyRegexp{
        var regexp: RubyRegexp =  new RubyRegexp(runtime);
        regexp.initialize(pattern, options, quote);
        return regexp;
    }

    public  function warn(message:String):void{
        getRuntime().getWarnings().warn(ID.MISCELLANEOUS, message);
    }

    public  function kcode():IRubyObject{
        return (!isKCodeDefault() && kcode != null) ? getRuntime().newString(kcode.name()) : getRuntime().getNil();
    }

    public override function getNativeTypeIndex():int{
        return ClassIndex.REGEXP;
    }
    
    public  function getPattern():String{
        return pattern;
    }

    private  function check():void{
        if (pattern == null || str == null) throw getRuntime().newTypeError("uninitialized Regexp");
    }

    public override function hash():RubyFixnum{
        check();
        var hashval: int =  pattern.getOptions();
        var len: int =  this.str.realSize;
        var p: int =  this.str.begin;
        while (len-->0) {
            hashval = hashval * 33 + str.bytes[p++];
        }
        hashval = hashval + (hashval>>5);
        return getRuntime().newFixnum(hashval);
    }

    public override function op_equal(other:IRubyObject):IRubyObject{
        if(this == other) return getRuntime().getTrue();
        if(!(other instanceof RubyRegexp)) return getRuntime().getFalse();
        var otherRegex: RubyRegexp =  RubyRegexp(other);
        
        check();
        otherRegex.check();
        
        if(str.equal(otherRegex.str) && kcode == otherRegex.kcode &&
                                      pattern.getOptions() == otherRegex.pattern.getOptions()) {
            return getRuntime().getTrue();
        }
        return getRuntime().getFalse();
    }

    public  function op_match2(context:ThreadContext):IRubyObject{
        var line: IRubyObject =  context.getCurrentFrame().getLastLine();
        if(!(line instanceof RubyString)) {
            context.getCurrentFrame().setBackRef(getRuntime().getNil());
            return getRuntime().getNil();
        }
        var start: int =  search(context, RubyString(line), 0, false);
        if(start < 0) {
            return getRuntime().getNil();
        } else {
            return getRuntime().newFixnum(start);
        }
    }

    /** rb_reg_eqq
     * 
     */
    public  function eqq(context:ThreadContext, str:IRubyObject):IRubyObject{
        if(!(str instanceof RubyString)) str = str.checkStringType();

        if (str.isNil()) {
            context.getCurrentFrame().setBackRef(getRuntime().getNil());
            return getRuntime().getFalse();
        }
        var start: int =  search(context, RubyString(str), 0, false);
        return (start < 0) ? getRuntime().getFalse() : getRuntime().getTrue();
    }

    private static var REGEX_QUOTED: int =  1;
    private function initialize2(bytes:Array, options:int, quote:Boolean):void{
        if (!isTaint() && getRuntime().getSafeLevel() >= 4) throw getRuntime().newSecurityError("Insecure: can't modify regexp");
        checkFrozen();
        if (isLiteral()) throw getRuntime().newSecurityError("can't modify literal regexp");

        setKCode(options);

        var cache: HashMap =  getPatternCache();
        var pat: Regex =  cache.get(bytes);

        if (pat != null &&
            pat.getEncoding() == kcode.getEncoding() &&
            pat.getOptions() == (options & 0xf) &&
            ((pat.getUserOptions() & REGEX_QUOTED) != 0) == quote) { // cache hit
            pattern = pat;
        } else {
            if (quote) bytes = quote(bytes, getRuntime().getKCode());
            makeRegexp(bytes, bytes.begin, bytes.realSize, options & 0xf, kcode.getEncoding());
            if (quote) pattern.setUserOptions(REGEX_QUOTED);
            cache.put(bytes, pattern);
        }

        str = bytes;
    }

    private  function makeRegexp(bytes:String, start:int, len:int, flags:int, enc:int):void{
        try {
            pattern = new Regex(bytes.bytes, start, start + len, flags, enc, Syntax.DEFAULT, this);
        } catch (e:Exception) {
            rb_reg_raise(bytes.bytes, start, len, e.getMessage(), flags);
        }
    }

    private  function rb_reg_raise(s:Array, start:int, len:int, err:String, flags:int):void{
        throw getRuntime().newRegexpError(err + ": " + rb_reg_desc(s,start, len,flags));
    }

    private  function rb_reg_desc(s:Array, start:int, len:int, flags:int):String{
        var sb: String =  new String("/");
        rb_reg_expr_str(sb, s, start, len);
        sb.append("/");

        if((flags & ReOptions.RE_OPTION_MULTILINE) != 0) sb.append("m");
        if((flags & ReOptions.RE_OPTION_IGNORECASE) != 0) sb.append("i");
        if((flags & ReOptions.RE_OPTION_EXTENDED) != 0) sb.append("x");

        if (kcode != null && !isKCodeDefault()) {
            sb.append(kcode.name().charAt(0));
        }
        return sb;
    }

    private  function rb_reg_expr_str(sb:String, s:Array, start:int, len:int):void{
        var p,pend:int;
        var need_escape: Boolean =  false;
        p = start;
        pend = start+len;
        var enc: Encoding =  kcode.getEncoding();
        while(p<pend) {
            if(s[p] == '/' || (!(' ' == s[p] || (!Character.isWhitespace(s[p]) && 
                                                 !Character.isISOControl(s[p]))) && 
                               enc.length(s[p])==1)) {
                need_escape = true;
                break;
            }
            p += enc.length(s[p]);
        }
        if(!need_escape) {
            sb.append(new String(s,start,len,false).toString());
        } else {
            p = 0;
            while(p < pend) {
                if(s[p] == '\\') {
                    var n: int =  enc.length(s[p+1]) + 1;
                    sb.append(new String(s,p,n,false).toString());
                    p += n;
                    continue;
                } else if(s[p] == '/') {
                    sb.append("\\/");
                } else if(enc.length(s[p])!=1) {
                    sb.append(new String(s,p,enc.length(s[p]),false).toString());
                    p += enc.length(s[p]);
                    continue;
                } else if((' ' == s[p] || (!Character.isWhitespace(s[p]) && 
                                           !Character.isISOControl(s[p])))) {
                    sb.append((char)(s[p]&0xFF));
                } else if(!Character.isWhitespace((char)(s[p]&0xFF))) {
                    sb.append('\\');
                    sb.append(Integer.toString((s[p]&0377),8));
                } else {
                    sb.append((char)(s[p]&0xFF));
                }
                p++;
            }
        }
    }    

    /** rb_reg_init_copy
     */
    public override function initialize_copy(re:IRubyObject):IRubyObject{
        if(this == re) return this;

        checkFrozen();

        if (getMetaClass().getRealClass() != re.getMetaClass().getRealClass()) {
            throw getRuntime().newTypeError("wrong argument type");
	    }

        var regexp: RubyRegexp =  RubyRegexp(re);
        regexp.check();

        initialize(regexp.str, regexp.getOptions(), false);

        return this;
    }

    /** rb_set_kcode
     */
    private  function getKcode():int{
        if(kcode == KCode.NONE) {
            return 16;
        } else if(kcode == KCode.EUC) {
            return 32;
        } else if(kcode == KCode.SJIS) {
            return 48;
        } else if(kcode == KCode.UTF8) {
            return 64;
        }
        return 0;
    }
    
    /**
     */
    private  function setKCode(options:int):void{
        clearKCodeDefault();
        switch(options & ~0xf) {
        case 0:
        default:
            setKCodeDefault();
            kcode = getRuntime().getKCode();
            break;
        case 16:
            kcode = KCode.NONE;
            break;
        case 32:
            kcode = KCode.EUC;
            break;
        case 48:
            kcode = KCode.SJIS;
            break;
        case 64:
            kcode = KCode.UTF8;
            break;
        }        
    }

    /** rb_reg_options
     */
    private  function getOptions():int{
        check();
        var options: int =  (pattern.getOptions() & (RE_OPTION_IGNORECASE|RE_OPTION_MULTILINE|RE_OPTION_EXTENDED));
        if(!isKCodeDefault()) {
            options |= getKcode();
        }
        return options;
    }

    /** rb_reg_initialize_m
     */
    public  function initialize_m(args:Array):IRubyObject{
        var bytes:String;
        var regexFlags: int =  0;

        if(args[0] instanceof RubyRegexp) {
            if(args.length > 1) {
                getRuntime().getWarnings().warn(ID.REGEXP_IGNORED_FLAGS, "flags" + (args.length == 3 ? " and encoding" : "") + " ignored");
            }
            var regexp: RubyRegexp =  RubyRegexp(args[0]);
            regexp.check();

            regexFlags = regexp.pattern.getOptions() & 0xF;
            if (!regexp.isKCodeDefault() && regexp.kcode != null && regexp.kcode != KCode.NIL) {
                if (regexp.kcode == KCode.NONE) {
                    regexFlags |= 16;
                } else if (regexp.kcode == KCode.EUC) {
                    regexFlags |= 32;
                } else if (regexp.kcode == KCode.SJIS) {
                    regexFlags |= 48;
                } else if (regexp.kcode == KCode.UTF8) {
                    regexFlags |= 64;
                }                
            }
            bytes = regexp.str;
        } else {
            if (args.length >= 2) {
                if (args[1] instanceof RubyFixnum) {
                    regexFlags = RubyNumeric.fix2int(args[1]);
                } else if (args[1].isTrue()) {
                    regexFlags = RE_OPTION_IGNORECASE;
                }
            }
            if (args.length == 3 && !args[2].isNil()) {
                var kcodeBytes: String =  args[2].convertToString().getString();
                var first: char =  kcodeBytes.length() > 0 ? kcodeBytes.charAt(0) : 0;
                regexFlags &= ~0x70;
                switch (first) {
                case 'n': case 'N':
                    regexFlags |= 16;
                    break;
                case 'e': case 'E':
                    regexFlags |= 32;
                    break;
                case 's': case 'S':
                    regexFlags |= 48;
                    break;
                case 'u': case 'U':
                    regexFlags |= 64;
                    break;
                default:
                    break;
                }
            }
            bytes = args[0].convertToString().getString();
        }
        initialize(bytes, regexFlags, false);

        return this;
    }

    public static function newInstance(recv:IRubyObject, args:Array):RubyRegexp{
        var klass: RubyClass =  RubyClass(recv);
        
        var re: RubyRegexp =  RubyRegexp( klass.allocate() );
        re.callInit(args, Block.NULL_BLOCK);
        
        return re;
    }

    public  function options():IRubyObject{
        return getRuntime().newFixnum(getOptions());
    }
    
    /** rb_reg_search
     */
    public  function search(context:ThreadContext, str:RubyString, pos:int, reverse:Boolean):int{
        var runtime: Ruby =  getRuntime();
        var frame: Frame =  context.getCurrentFrame();

        var value: String =  str.getString();
        if (pos > value.realSize || pos < 0) {
            frame.setBackRef(runtime.getNil());
            return -1;
        }
        
        check();
        
        var range: int =  reverse ? -pos : value.realSize - pos;
        
        var matcher: Matcher =  pattern.matcher(value.bytes, value.begin, value.begin + value.realSize);
        
        var result: int =  matcher.search(value.begin + pos, 
                                    value.begin + pos + range,
                                    Option.NONE);
        
        if (result < 0) {
            frame.setBackRef(runtime.getNil());
            return result;
        }
        
        updateBackRef(str, frame, matcher);

        return result;
    }
    
    function updateBackRef(str:RubyString, frame:Frame, matcher:Object):RubyMatchData{
        var backref: IRubyObject =  frame.getBackRef();
        var match:RubyMatchData;
        if (backref == null || backref.isNil() || RubyMatchData(backref).used()) {
            match = new RubyMatchData(getRuntime());
        } else {
            match = RubyMatchData(backref);
            if(getRuntime().getSafeLevel() >= 3) {
                match.setTaint(true);
            } else {
                match.setTaint(false);
            }
        }
        
        match.regs = matcher.getRegion(); // lazy, null when no groups defined
        match.begin = matcher.getBegin();
        match.end = matcher.getEnd();
        
        match.str = RubyString(str.strDup()).freeze();
        match.pattern = pattern;

        frame.setBackRef(match);
        
        match.infectBy(this);
        match.infectBy(str);
        return match;
    }

    /** rb_reg_match
     * 
     */
    public override function op_match(context:ThreadContext, str:IRubyObject):IRubyObject{
        var start:int;
        if(str.isNil()) {
            context.getCurrentFrame().setBackRef(getRuntime().getNil());
            return str;
        }
        
        start = search(context, str.convertToString(), 0, false);

        if(start < 0) return getRuntime().getNil();

        return RubyFixnum.newFixnum(getRuntime(), start);
    }

    /** rb_reg_match_m
     * 
     */
    public  function match_m(context:ThreadContext, str:IRubyObject):IRubyObject{
        if(op_match(context, str).isNil()) {
            return getRuntime().getNil();
        }
        var result: IRubyObject =   context.getCurrentFrame().getBackRef();
        if(result instanceof RubyMatchData) {
            RubyMatchData(result).use0();
        }
        return result;
    }


    public  function regsub(str:RubyString, src:RubyString, matcher:Object):RubyString{
        var regs: Region =  matcher.getRegion();
        var mbeg: int =  matcher.getBegin();
        var mend: int =  matcher.getEnd();
        
        var p,s,e:int;
        p = s = 0;
        var no: int =  -1;
        var bs: String =  str.getString();
        var srcbs: String =  src.getString();
        e = bs.length();
        var val: RubyString =  null;
        var enc: Encoding =  kcode.getEncoding();

        var beg, end:int;
        while(s < e) {
            var ss: int =  s;
            var c: char =  bs.charAt(s++);
            if(enc.length(c) != 1) {
                s += enc.length(c) - 1;
                continue;
            }
            if (c != '\\' || s == e) continue;
            if (val == null) val = RubyString.newString(getRuntime(), new String(ss - p));

            val.cat(bs.bytes, bs.begin + p, ss - p);
            c = bs.charAt(s++);
            p = s;
            
            switch(c) {
            case '0': case '1': case '2': case '3': case '4':
            case '5': case '6': case '7': case '8': case '9':
                no = c - '0';
                break;
            case '&':
                no = 0;
                break;
            case '`':
                beg = regs == null ? mbeg : regs.beg[0];
                val.cat(srcbs.bytes, srcbs.begin, beg);
                continue;

            case '\'':
                end = regs == null ? mend : regs.end[0];
                val.cat(srcbs.bytes, srcbs.begin + end, src.getString().realSize - end);
                continue;

            case '+':
                if (regs == null) {
                    if (mbeg == -1) {
                        no = 0;
                        continue;
                    }
                } else {
                    no = regs.numRegs-1;
                    while(regs.beg[no] == -1 && no > 0) no--;
                    if (no == 0) continue;
                }
                break;
            case '\\':
                val.cat(bs.bytes, s - 1, 1);
                continue;
            default:
                val.cat(bs.bytes, s - 2, 2);
                continue;
            }

            if (regs != null) {
                if (no >= 0) {
                    if (no >= regs.numRegs || regs.beg[no] == -1) continue;
                    val.cat(srcbs.bytes, srcbs.begin + regs.beg[no], regs.end[no] - regs.beg[no]);
                }
            } else {
                if (no != 0 || mbeg == -1) continue;
                val.cat(srcbs.bytes, srcbs.begin + mbeg, mend - mbeg);
            }
        }

        if(p < e) {
            if(val == null) {
                val = RubyString.newString(getRuntime(), bs.makeShared(p, e-p));
            } else {
                val.cat(bs.bytes, bs.begin + p, e - p);
            }
        }
        if (val == null) return str;

        return val;
    }

    function adjustStartPos(str:RubyString, pos:int, reverse:Boolean):int{
        check();
        var value: String =  str.getString();
        return pattern.adjustStartPosition(value.bytes, value.begin, value.realSize, pos, reverse);
    }

    public  function casefold_p():IRubyObject{
        check();
        if ((pattern.getOptions() & RE_OPTION_IGNORECASE) != 0) {
            return getRuntime().getTrue();
        }
        return getRuntime().getFalse();
    }

    /** rb_reg_source
     * 
     */
    public  function source():IRubyObject{
        check();
        var str: RubyString =  RubyString.newStringShared(getRuntime(), this.str);
        if(isTaint()) {
            str.taint();
        }
        return str;
    }

    function length():int{
        return str.realSize;
    }

    /** rb_reg_inspect
     *
     */
    public override function inspect():IRubyObject{
        check();
        return getRuntime().newString(String.create(rb_reg_desc(str.bytes, str.begin, str.realSize, pattern.getOptions()).toString()));
    }

    private static var EMBEDDABLE: int =  RE_OPTION_MULTILINE|RE_OPTION_IGNORECASE|RE_OPTION_EXTENDED;

    public override function to_s():IRubyObject{
        var ss: RubyString =  getRuntime().newString("(?");
        check();
        var options: int =  pattern.getOptions();
        var p: int =  str.begin;
        var l: int =  str.realSize;
        var _str: Array =  str.bytes;


        again: do {
            if(l >= 4 && _str[p] == '(' && _str[p+1] == '?') {
                var err: Boolean =  true;
                p += 2;
                if((l -= 2) > 0) {
                    do {
                        if(_str[p] == 'm') {
                            options |= RE_OPTION_MULTILINE;
                        } else if(_str[p] == 'i') {
                            options |= RE_OPTION_IGNORECASE;
                        } else if(_str[p] == 'x') {
                            options |= RE_OPTION_EXTENDED;
                        } else {
                            break;
                        }
                        p++;
                    } while(--l > 0);
                }
                if(l > 1 && _str[p] == '-') {
                    ++p;
                    --l;
                    do {
                        if(_str[p] == 'm') {
                            options &= ~RE_OPTION_MULTILINE;
                        } else if(_str[p] == 'i') {
                            options &= ~RE_OPTION_IGNORECASE;
                        } else if(_str[p] == 'x') {
                            options &= ~RE_OPTION_EXTENDED;
                        } else {
                            break;
                        }
                        p++;
                    } while(--l > 0);
                }
                if(_str[p] == ')') {
                    --l;
                    ++p;
                    continue again;
                }
                if(_str[p] == ':' && _str[p+l-1] == ')') {
                    try {
                        new Regex(_str,++p,l-=2,Option.DEFAULT,kcode.getEncoding(),Syntax.DEFAULT);
                        err = false;
                    } catch (e:Exception) {
                        err = true;
                    }
                }
                if(err) {
                    options = pattern.getOptions();
                    p = str.begin;
                    l = str.realSize;
                }
            }

            if((options & RE_OPTION_MULTILINE)!=0) ss.cat(M_CHAR);
            if((options & RE_OPTION_IGNORECASE)!=0) ss.cat(I_CHAR);
            if((options & RE_OPTION_EXTENDED)!=0) ss.cat(X_CHAR);

            if((options&EMBEDDABLE) != EMBEDDABLE) {
                ss.cat(DASH);
                if((options & RE_OPTION_MULTILINE)==0) ss.cat(M_CHAR);
                if((options & RE_OPTION_IGNORECASE)==0) ss.cat(I_CHAR);
                if((options & RE_OPTION_EXTENDED)==0) ss.cat(X_CHAR);
            }
            ss.cat(COLON);
            rb_reg_expr_str(ss,p,l);
            ss.cat(R_PAREN);
            ss.infectBy(this);
            return ss;
        } while(true);
    }

    private  function ISPRINT(c:int):Boolean{
        return ISPRINT((c&0xFF));
    }

    private  function ISPRINT2(c:int):Boolean{
        return (' ' == c || (!Character.isWhitespace(c) && !Character.isISOControl(c)));
    }

    private  function rb_reg_expr_str2(ss:RubyString, s:int, l:int):void{
        var p: int =  s;
        var pend: int =  l;
        var need_escape: Boolean =  false;
        while(p<pend) {
            if(str.bytes[p] == '/' || (!ISPRINT(str.bytes[p]) && kcode.getEncoding().length(str.bytes[p]) == 1)) {
                need_escape = true;
                break;
            }
            p += kcode.getEncoding().length(str.bytes[p]);
        }
        if(!need_escape) {
            ss.cat(str.bytes,s,l);
        } else {
            p = s; 
            while(p<pend) {
                if(str.bytes[p] == '\\') {
                    var n: int =  kcode.getEncoding().length(str.bytes[p+1]) + 1;
                    ss.cat(str.bytes,p,n);
                    p += n;
                    continue;
                } else if(str.bytes[p] == '/') {
                    var c: char =  '\\';
                    ss.cat(c);
                    ss.cat(str.bytes,p,1);
                } else if(kcode.getEncoding().length(str.bytes[p]) != 1) {
                    ss.cat(str.bytes,p,kcode.getEncoding().length(str.bytes[p]));
                    p += kcode.getEncoding().length(str.bytes[p]);
                    continue;
                } else if(ISPRINT(str.bytes[p])) {
                    ss.cat(str.bytes,p,1);
                } else if(!Character.isWhitespace(str.bytes[p])) {
                    ss.cat(String.create(Integer.toString(str.bytes[p]&0377,8)));
                } else {
                    ss.cat(str.bytes,p,1);
                }
                p++;
            }
        }
    }

    /** rb_reg_s_quote
     * 
     */
    public static function quote(recv:IRubyObject, args:Array):RubyString{
        var kcode: IRubyObject =  args.length == 2 ? args[1] : null;
        var str: IRubyObject =  args[0];
        var code: KCode =  recv.getRuntime().getKCode();

        if (kcode != null && !kcode.isNil()) {
            code = KCode.create(recv.getRuntime(), kcode.toString());
        }
        
        var src: RubyString =  str.convertToString();
        var dst: RubyString =  RubyString.newString(recv.getRuntime(), quote(src.getString(), code));
        dst.infectBy(src);
        return dst;
    }

    /** rb_reg_quote
     *
     */
    public static function quote2(str:String, kcode:KCode):String{
        var bs: String =  str;
        var tix: int =  0;
        var s: int =  bs.begin;
        var c:char;
        var send: int =  s+bs.length();
        var enc: Encoding =  kcode.getEncoding();
        meta_found: do {
            for(; s<send; s++) {
                c = int(bs.bytes[s]&0xFF);
                if(enc.length(c) != 1) {
                    var n: int =  enc.length(c);
                    while(n-- > 0 && s < send) {
                        s++;
                    }
                    s--;
                    continue;
                }
                switch (c) {
                case '[': case ']': case '{': case '}':
                case '(': case ')': case '|': case '-':
                case '*': case '.': case '\\':
                case '?': case '+': case '^': case '$':
                case ' ': case '#':
                case '\t': case '\f': case '\n': case '\r':
                    break meta_found;
                }
            }
            return bs;
        } while(false);
        var b1: String =  new String(send*2);
        System.arraycopy(bs.bytes,bs.begin,b1.bytes,b1.begin,s-bs.begin);
        tix += (s-bs.begin);

        for(; s<send; s++) {
            c = int(bs.bytes[s]&0xFF);
            if(enc.length(c) != 1) {
                var n: int =  enc.length(c);
                while(n-- > 0 && s < send) {
                    b1.bytes[tix++] = bs.bytes[s++];
                }
                s--;
                continue;
            }

            switch(c) {
            case '[': case ']': case '{': case '}':
            case '(': case ')': case '|': case '-':
            case '*': case '.': case '\\':
            case '?': case '+': case '^': case '$':
            case '#':
                b1.bytes[tix++] = '\\';
                break;
            case ' ':
                b1.bytes[tix++] = '\\';
                b1.bytes[tix++] = ' ';
                continue;
            case '\t':
                b1.bytes[tix++] = '\\';
                b1.bytes[tix++] = 't';
                 continue;
            case '\n':
                b1.bytes[tix++] = '\\';
                b1.bytes[tix++] = 'n';
                continue;
            case '\r':
                b1.bytes[tix++] = '\\';
                b1.bytes[tix++] = 'r';
                continue;
            case '\f':
                b1.bytes[tix++] = '\\';
                b1.bytes[tix++] = 'f';
                continue;
            }
            b1.bytes[tix++] = c;
        }
        b1.realSize = tix;
        return b1;
    }


    /** rb_reg_nth_match
     *
     */
    public static function nth_match(nth:int, match:IRubyObject):IRubyObject{
        if (match.isNil()) return match;
        var m: RubyMatchData =  RubyMatchData(match);

        var start, end:int;
        
        if (m.regs == null) {
            if (nth >= 1) return match.getRuntime().getNil();
            if (nth < 0 && ++nth <= 0) return match.getRuntime().getNil();
            start = m.begin;
            end = m.end;
        } else {
            if (nth >= m.regs.numRegs) return match.getRuntime().getNil();
            if (nth < 0 && (nth+=m.regs.numRegs) <= 0) return match.getRuntime().getNil();
            start = m.regs.beg[nth];
            end = m.regs.end[nth];
        }
        
        if(start == -1) return match.getRuntime().getNil();

        var str: RubyString =  m.str.makeShared(start, end - start);
        str.infectBy(match);
        return str;
    }

    /** rb_reg_last_match
     *
     */
    public static function last_match(match:IRubyObject):IRubyObject{
        return nth_match(0, match);
    }

    /** rb_reg_s_last_match
     *
     */
    public static function last_match_s(context:ThreadContext, recv:IRubyObject, args:Array):IRubyObject{
        if (args.length == 1) {
            return nth_match(RubyNumeric.fix2int(args[0]), context.getCurrentFrame().getBackRef());
        }

        var result: IRubyObject =  context.getCurrentFrame().getBackRef();

        if (result instanceof RubyMatchData) RubyMatchData(result).use0();
        return result;
    }

    /** rb_reg_match_pre
     *
     */
    public static function match_pre(match:IRubyObject):IRubyObject{
        if (match.isNil()) return match;
        var m: RubyMatchData =  RubyMatchData(match);
        
        var beg: int =  m.regs == null ? m.begin : m.regs.beg[0];
        
        if(beg == -1) match.getRuntime().getNil(); 

        var str: RubyString =  m.str.makeShared(0, beg);
        str.infectBy(match);
        return str;
    }

    /** rb_reg_match_post
     *
     */
    public static function match_post(match:IRubyObject):IRubyObject{
        if (match.isNil()) return match;
        var m: RubyMatchData =  RubyMatchData(match);

        var end:int;
        if (m.regs == null) {
            if (m.begin == -1) return match.getRuntime().getNil();
            end = m.end;
        } else {
            if (m.regs.beg[0] == -1) return match.getRuntime().getNil();
            end = m.regs.end[0];
        }
        
        var str: RubyString =  m.str.makeShared(end, m.str.getString().realSize - end);
        str.infectBy(match);
        return str;
    }

    /** rb_reg_match_last
     *
     */
    public static function match_last(match:IRubyObject):IRubyObject{
        if (match.isNil()) return match;
        var m: RubyMatchData =  RubyMatchData(match);

        if (m.regs == null || m.regs.beg[0] == -1) return match.getRuntime().getNil();

        var i:int;
        for (i=m.regs.numRegs-1; m.regs.beg[i]==-1 && i>0; i--);
        if (i == 0) return match.getRuntime().getNil();
        
        return nth_match(i,match);
    }

    /** rb_reg_s_union
     *
     */
    public static function union(context:ThreadContext, recv:IRubyObject, args:Array):IRubyObject{
        if (args.length == 0) {
            return newRegexp(recv.getRuntime(), String.create("(?!)"), 0, false);
        } else if (args.length == 1) {
            var v: IRubyObject =  TypeConverter.convertToTypeWithCheck(args[0], recv.getRuntime().getRegexp(), 0, "to_regexp");
            if(!v.isNil()) {
                return v;
            } else {
                // newInstance here
                return newRegexp(recv.getRuntime(), quote(recv,args).getString(), 0, false);
            }
        } else {
            var kcode: KCode =  null;
            var kcode_re: IRubyObject =  recv.getRuntime().getNil();
            var source: RubyString =  recv.getRuntime().newString("");
            var _args: Array =  new IRubyObject[3];

            for ( var int:i = 0; i < args.length; i++) {
                if(0 < i) {
                    source.cat(PIPE);
                }
                var v: IRubyObject =  TypeConverter.convertToTypeWithCheck(args[i], recv.getRuntime().getRegexp(), 0, "to_regexp");
                if (!v.isNil()) {
                    if (!RubyRegexp(v).isKCodeDefault()) {
                        if (kcode == null) {
                            kcode_re = v;
                            kcode = RubyRegexp(v).kcode;
                        } else if (RubyRegexp(v).kcode != kcode) {
                            var str1: IRubyObject =  kcode_re.inspect();
                            var str2: IRubyObject =  v.inspect();
                            throw recv.getRuntime().newArgumentError("mixed kcode " + str1 + " and " + str2);
                        }
                    }
                    v = RubyRegexp(v).to_s();
                } else {
                    v = quote(recv, [args[i]]);
                }
                source.append(v);
            }

            _args[0] = source;
            _args[1] = recv.getRuntime().getNil();
            if (kcode == null) {
                _args[2] = recv.getRuntime().getNil();
            } else if (kcode == KCode.NONE) {
                _args[2] = recv.getRuntime().newString("n");
            } else if (kcode == KCode.EUC) {
                _args[2] = recv.getRuntime().newString("e");
            } else if (kcode == KCode.SJIS) {
                _args[2] = recv.getRuntime().newString("s");
            } else if (kcode == KCode.UTF8) {
                _args[2] = recv.getRuntime().newString("u");
            }
            return recv.callMethod(context, "new", _args);
        }
    }

/*     public static function unmarshalFrom(input:UnmarshalStream):RubyRegexp{
        var result: RubyRegexp =  newRegexp(input.getRuntime(), input.unmarshalString(), input.unmarshalInt(), false);
        input.registerLinkTarget(result);
        return result;
    }

    public static function marshalTo(regexp:RubyRegexp, output:MarshalStream):void{
        output.registerLinkTarget(regexp);
        output.writeString(new String(regexp.str.bytes,regexp.str.begin,regexp.str.realSize));
        output.writeInt(regexp.pattern.getOptions() & EMBEDDABLE);
    } */
}
}